package cn.tedu.mall.order.service.imp;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.domain.CsmallAuthenticationInfo;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.order.mapper.OmsOrderItemMapper;
import cn.tedu.mall.order.mapper.OmsOrderMapper;
import cn.tedu.mall.order.service.IOmsCartService;
import cn.tedu.mall.order.service.IOmsOrderService;
import cn.tedu.mall.order.utils.IdGeneratorUtils;
import cn.tedu.mall.pojo.order.dto.OrderAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.order.dto.OrderListTimeDTO;
import cn.tedu.mall.pojo.order.dto.OrderStateUpdateDTO;
import cn.tedu.mall.pojo.order.model.OmsCart;
import cn.tedu.mall.pojo.order.model.OmsOrder;
import cn.tedu.mall.pojo.order.model.OmsOrderItem;
import cn.tedu.mall.pojo.order.vo.OrderAddVO;
import cn.tedu.mall.pojo.order.vo.OrderDetailVO;
import cn.tedu.mall.pojo.order.vo.OrderListVO;
import cn.tedu.mall.product.service.order.IForOrderSkuService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {

    @Autowired
    private OmsOrderMapper orderMapper;
    @Autowired
    private OmsOrderItemMapper orderItemMapper;
    @DubboReference
    private IForOrderSkuService dubboSkuService;
    @Autowired
    private IOmsCartService omsCartService;


    // 当前生成订单的方法为分布式事务
    // Seata保证整个业务逻辑层运行的过程中所有数据库要么都成功,要么都失败
    @GlobalTransactional
    @Override
    public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
        // 首先先实例化能新增到数据库的OmsOrder类
        OmsOrder omsOrder = new OmsOrder();
        // 业务逻辑层参数OrderAddDTO数据有限,其他OmsOrder需要的数据我们需要自己收集
        BeanUtils.copyProperties(orderAddDTO,omsOrder);
        // 因为收集OmsOrder数据代码较多,所以我们单独编写一个方法实现
        loadOrder(omsOrder);
        // 获得当前订单中所有商品的sku
        List<OrderItemAddDTO> orderItemDTOs = orderAddDTO.getOrderItems();
        if (orderItemDTOs == null || orderItemDTOs.isEmpty()) {
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"你没有选择购物车商品");
        }
        // 上面是实例化一个OmsOrderItem泛型集合,以备使用
        List<OmsOrderItem> omsOrderItems = new ArrayList<>();
        // 遍历订单用包含的所有商品的集合
        for (OrderItemAddDTO orderItem:orderItemDTOs){
            // 遍历操作目标
            // 将OmsOrderItem需要但是OrderItemAddDTO没有的属性补充上
            OmsOrderItem omsOrderItem = new OmsOrderItem();
            // 同名属性赋值
            BeanUtils.copyProperties(orderItem,omsOrderItem);
            // 定义一个收集OrderItem属性的方法
            loadOrderItem(omsOrderItem);
            // 赋值订单id
            omsOrderItem.setOrderId(omsOrder.getId());
            // 将补充好的属性的OmsOrderItem对象添加到omsOrderItems中
            omsOrderItems.add(omsOrderItem);
            // 减少库存数
            // 获得skuId

            Long skuId = omsOrderItem.getSkuId();
            // 获得商品数量
            Integer quantity = omsOrderItem.getQuantity();
            // Dubbo调用减少库存的方法
            int result = dubboSkuService.reduceStockNum(skuId,quantity);
            // result是减少库存方法执行后影响数据库的行数,如果是0,表示数据没有变化,一般是库存不足导致的
            if (result == 0){
                log.warn("商品skuId:{}库存不足",skuId);
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"库存不足");
            }
            // 删除购物车
            OmsCart omsCart = new OmsCart();
            omsCart.setSkuId(skuId);
            omsCart.setUserId(omsOrder.getUserId());
            omsCartService.removeUserCarts(omsCart);
        }
        // 执行将所有订单中的商品新增到oms_order_item表中
        orderItemMapper.insertOrderItem(omsOrderItems);
        // 新增生成好的订单到oms_order表
        orderMapper.insertOrder(omsOrder);
        // 到此为止,新增完成了
        // 下面要完成用户生成订单预览页面,这个页面中大部分信息可以前端自己处理
        // 我们需要将一些有后端生成的代码发送给前端,这个类是OrderAddVO
        OrderAddVO addVO = new OrderAddVO();
        addVO.setId(omsOrder.getId());
        addVO.setSn(omsOrder.getSn());
        addVO.setCreateTime(omsOrder.getGmtCreate());
        addVO.setPayAmount(omsOrder.getAmountOfActualPay());
        return addVO;
    }

    /**
     * 收集OmsOrderItem的数据
     * @param omsOrderItem
     */
    private void loadOrderItem(OmsOrderItem omsOrderItem) {
        // 判断id是否为空
        if (omsOrderItem.getId() == null) {
            // 从leaf中分离id
            Long id = IdGeneratorUtils.getDistributeId("order");
            omsOrderItem.setId(id);
        }
        // 商品没有skuId的抛异常Seata会回滚
        if (omsOrderItem.getSkuId() == null) {
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"skuId不能为空");
        }
    }

    /**
     *
     * 收集OmsOrder的数据
     * @param omsOrder
     */
    private void loadOrder(OmsOrder omsOrder) {
        // 判断id是否为空
        if (omsOrder.getId() == null) {
            // 从leaf中分离id
            Long id = IdGeneratorUtils.getDistributeId("order");
            omsOrder.setId(id);
        }
        // 判读userId并为其赋值
        if (omsOrder.getId() == null) {
            // jwt解析结果中获得userId
            Long userId = getUserId();
            omsOrder.setUserId(userId);
        }
        // 判断sn是否为空并赋值
        if (omsOrder.getSn() == null) {
            // sn是给用户看的订单编号,使用UUID生成即可
            omsOrder.setSn(UUID.randomUUID().toString());
        }
        // 判断state并赋值
        if (omsOrder.getState() == null) {
            // state:0表示订单未支付,新创建的订单都是未支付
            omsOrder.setState(0);
        }
        // 订单生成时,订单创建时间,数据创建时间和最后修改时间是同一时刻
        // 所以我们手动赋值,保证它们的一致性
        if (omsOrder.getGmtCreate() == null){
            omsOrder.setGmtCreate(LocalDateTime.now());
        }
        if (omsOrder.getGmtOrder() == null) {
            omsOrder.setGmtOrder(omsOrder.getGmtCreate());
        }
        if (omsOrder.getGmtModified() == null) {
            omsOrder.setGmtModified(omsOrder.getGmtCreate());
        }
        // 下面是金额的判断和计算
        // 基本逻辑是原始金额加运费减优惠计算出最终金额
        // 优惠的判断
        if (omsOrder.getAmountOfDiscount() == null) {
            // 为了防止计算时发生浮点偏移误差,我们使用BigDecimal类型
            omsOrder.setAmountOfDiscount(new BigDecimal(0.0));
        }
        // 运费的判断
        if (omsOrder.getAmountOfFreight() == null) {
            omsOrder.setAmountOfFreight(new BigDecimal(0.0));
        }
        // 开始计算订单金额
        // 如果没有初始订单金额的数值,表示前段传值不正确,直接发生异常
        if (omsOrder.getAmountOfOriginalPrice() == null) {
            throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"请确定购买的商品");
        }
        // 判断实际支付金额
        if (omsOrder.getAmountOfActualPay() == null) {
            // 实际金额=原始金额+运费-优惠
            // 获得各项金额
            BigDecimal originalPrice = omsOrder.getAmountOfOriginalPrice();
            BigDecimal discount = omsOrder.getAmountOfDiscount();
            BigDecimal freight = omsOrder.getAmountOfFreight();
            BigDecimal actualPay = originalPrice.add(freight).subtract(discount);
            omsOrder.setAmountOfActualPay(actualPay);
        }
    }

    @Override
    public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
        // OrderStateUpdateDTO参数只有id和state两个属性,支持validation验证
        OmsOrder omsOrder = new OmsOrder();
        BeanUtils.copyProperties(orderStateUpdateDTO,omsOrder);
        orderMapper.updateOrderById(omsOrder);
    }

    @Override
    public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
        // 本业务的逻辑中比较特殊的树时间的判断,单独编写一个方法校验时间或者添加默认时间
        validTimesAndLoadTime(orderListTimeDTO);
        // 获取userId
        Long userId = getUserId();
        // 将userId赋值给orderListTimeDTO
        orderListTimeDTO.setUserId(userId);
        // 开启分页查询
        // 如果想要分页信息的可以在这里编写代码
        PageHelper.startPage(orderListTimeDTO.getPage(),orderListTimeDTO.getPageSize());
        // 执行查询
        List<OrderListVO> list = orderMapper.selectOrdersBetweenTimes(orderListTimeDTO);
        PageInfo<OrderListVO> orderListVOPageInfo = new PageInfo<>(list);
        return JsonPage.restPage(orderListVOPageInfo);
    }

    /**
     * 校验时间或者添加默认时间
     * @param orderListTimeDTO
     */
    private void validTimesAndLoadTime(OrderListTimeDTO orderListTimeDTO) {
        // 先取出起始时间对象和结束时间对象
        LocalDateTime start = orderListTimeDTO.getStartTime();
        LocalDateTime end = orderListTimeDTO.getEndTime();
        // 如果start或end有任何一个为空,默认查询近一个月订单
        if (start == null || end == null){
            // 起始时间定一个月前
            start = LocalDateTime.now().minusMonths(1);
            // 结束时间是现在
            end = LocalDateTime.now();
            // 将修修改好的起始和结束时间赋值给orderListTimeDTO
            orderListTimeDTO.setStartTime(start);
            orderListTimeDTO.setEndTime(end);
        }else {
            // 如果 起始时间和结束时间都有值,判断结束时间是要小于起始时间要发生异常
            if (end.toInstant(ZoneOffset.of("+8")).toEpochMilli() < 
                    start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,"查询的结束时间因大于起始时间");
            }
        }
    }

    @Override
    public OrderDetailVO getOrderDetail(Long id) {
        return null;
    }

    // 业务逻辑层中可能有多个方法需要获取当前用户信息
    /**
     * 从SpringSecurity中获取用户信息
     * @return 用户信息
     */
    public CsmallAuthenticationInfo getUserInfo(){
        // 从SpringSecurity框架容器中,获得当前用户的authenticationToken对象
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder
                        .getContext().getAuthentication();
        // 判断获取的对象是不是null
        if (authenticationToken != null){
            CsmallAuthenticationInfo csmallAuthenticationInfo =
                    (CsmallAuthenticationInfo) authenticationToken.getCredentials();
            return csmallAuthenticationInfo;
        }
        throw new CoolSharkServiceException(ResponseCode.UNAUTHORIZED,"没有登录信息");
    }

    /**
     * 单纯获取当前登录对象的id
     * @return
     */
    public Long getUserId(){
        Long userId = getUserInfo().getId();
        return userId;
    }
}
